<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- Class -->
    <script>
        /* 两种定义方式 - 声明 & 表达式 */
        // console.log(C1); // 类声明不提升, 报错
        class C1 { }
        const C2 = class C2n { };
        let C3 = new class { }; // 立即执行Class
        console.log(C1, C2, C2.name, typeof C1);
        // console.log(C2n); // 该标识符不能再类表达式作用域外部访问, 报错

        /* constructor - 类构造函数 */
        {
            class Person {
                constructor(name) {
                    this.name = name;
                    // sayName 存在于不同实例上
                    this.sayName = function () {
                        console.log(this.name);
                    }
                }
                // 还定义了toString方法
                toString() {
                    return `I am ${this.name}.`;
                }
                // sayName 定义在原型上
                sayName() {
                    console.log('prototype');
                }
                // 静态类成员, 每个类上只能有一个, 定义在类本身上, 不会被实例继承
                static create() {
                    // 适合作为实例工厂, 使用随机数创造实例
                    return new Person(Math.floor(Math.random() * 100));
                }
            }
            // 类方法相当于对象属性, 可以使用变量(字符串/符号/计算值)作为键名
            const symbolKey = Symbol('symbolKey');
            // 类的方法都定义在原型上, 可用Object.assign()向类添加多个方法
            Object.assign(Person.prototype, {
                test1() { console.log(1); },
                test2() { console.log(2); },
                [symbolKey]() { console.log('symbolKey'); }
            });
            let p1 = new Person('lili');
            let p2 = new Person('lala');
            console.log(p1, p1.toString());
            // 类块中定义的方法都为原型方法
            p1.sayName();
            p1.__proto__.sayName();
            p1.test1();
            p1[symbolKey]();
            console.log(Person.create());
        }

        /* static - 静态类成员, 不会被实例继承 */
        {
            class Foo {
                static bar() {
                    console.log(this); // this指向Foo类本身
                }
            }
            let f = new Foo();
            Foo.bar();
            // f.bar(); // 报错, 静态类成员存在于类, 不存在实例
        }

        /* 属性新写法 */
        {
            class MyClass1 {
                _count = 0; // 定义实例属性
                static sProp = 1; // （新写法）定义静态属性
            }
            MyClass1.prop = 2; // （旧写法）定义静态属性
            let mc = new MyClass1();
            console.log(mc._count, MyClass1.sProp, MyClass1.prop);
        }
        /* 私有方法的定义 */
        {
            // 1 - 私有方法命名为Symbol值
            const _baz = Symbol('baz');
            class MyClass2 {
                // 2 - ( _ 下划线) 在命名上区别, 但外部仍可引用
                foo() { } // 公有
                _foo() { console.log('foo'); } // 私有
                _bar(a) {
                    bar.call(this, a);
                }
                [_baz]() { console.log('baz'); }

            }
            // 3 - 私有方法移出类, 因为类内部所有方法都对外可见
            function bar(a) {
                // console.log('bar');
                return this.snaf = a;
            }
            console.log(MyClass2);
            const mc2 = new MyClass2();
            mc2[_baz]();
        }

        /* 私有属性/方法的定义 */
        {
            // (#) 在命名上区别 / static关键字
            class MyClass3 {
                #count = 0;
                #fn() {
                    console.log(this.#count);
                }
            }
            const mc3 = new MyClass3();
            // console.log(mc3.#count); // 报错, 无法使用
            // mc3.#fn(); // 报错, 无法使用
            // must be declared in an enclosing class
        }

        /* 判断私有属性 */
        // 1 - try..catch, 2 - in 运算符

        /* new.target - 返回被new调用的构造函数 */
        {        // 子类继承父类时, new.target返回子类
            // 函数外部使用new.target报错
            class Rectangle {
                constructor(length, width) {
                    console.log(new.target === Rectangle);
                    // ...
                }
            }
            class Square extends Rectangle {
                constructor(length, width) {
                    super(length, width);
                }
            }
            var obj = new Square(3); // false, 说明Rectangle不是new出的类
            // 利用这个特点可以写出不能独立使用, 需要继承后才能使用的类
            class Shape {
                constructor() {
                    if (new.target === Shape) {
                        throw new Error('本类不能实例化');
                    }
                    else {
                        console.log('本类可以实例化');
                    }
                }
            }
            class _Rectangle extends Shape {
                constructor(length, width) {
                    super();
                    // ...
                }
            }
            var y = new _Rectangle(3, 4);  // 正确
            var x = new Shape();  // 报错
        }
    </script>

    <!-- Class的继承-->
    <script>
        /* extends 关键字 - 可以继承类, 也可以继承普通构造函数 */
        {
            class Vehicle {
                identifyPrototype() { console.log(this); }
                static identifyClass() { console.log(this); }
            }
            class Bus extends Vehicle { }
            // let Bus = class extends Vehicle{};
            function Fruit() { }
            class Apple extends Fruit { }
            const b = new Bus();
            const a = new Apple();
            console.log(b instanceof Vehicle, a instanceof Fruit); // true true
            b.identifyPrototype();
            Bus.identifyClass();
        }

        /* super 关键字 - 通过super调用父类构造函数*/
        {
            class Drink {
                constructor(name) {
                    this.sort = 'drink';
                    this.name = name;
                    Drink.prototype.show = 'showSth';
                }
                static identify(msg) {
                    console.log(msg);
                    console.log(this);
                }
            }
            class Coffee extends Drink {
                // 普通方法中, super指向父类的原型对象(父类.prototype), this指向当前子类实例
                constructor(name) {
                    // console.log(this); // 在调用super之前引用this, 报错
                    super(name); // 相当于super.constructor()
                    console.log(super.sort, super.show);
                    // 父类的实例属性sort, 无法通过super调用, 父类原型上的属性show则可以调用
                    console.log(this); // this指向当前子类实例
                }
                // 静态方法中, super指向父类, this指向当前子类
                static identify(msg) {
                    super.identify(msg);
                }
            }
            const c = new Coffee('black');
            Coffee.identify('msg');
        }

        /* 类的prototype & __proto__属性 */
        {
            class A { }
            class B extends A { }
            console.log(B.__proto__ === A);
            // __proto__表示构造函数的继承, 总是指向父类
            console.log(B.prototype.__proto__ === A.prototype);
            // prototype.__proto__, 表示方法的继承, 总是指向父类的prototype
            // 两条继承链: 
            // 作为一个对象, 子类的原型(__proto__)是父类; 
            // 作为一个构造函数, 子类的原型对象(prototype)是父类的原型对象(prototype)的实例
        }

        /* 原生构造函数的继承 */
        {
            class myArray extends Array {
                constructor(...args) {
                    super(...args)
                }
            }
            let arr = new myArray();
            arr.push(1, 2, 3);
            console.log(arr.length);
        }
    
        /* Minin - 多个对象合成一个新的对象 */
        
        
    
    </script>
</body>

</html>